import { $, h, Random, Time, /*type Context*/ } from "koishi"
import fsp from "fs/promises"

/*import type {} from "@koishijs/cache"
import type {} from "koishi-plugin-adapter-onebot"
import type {} from "koishi-plugin-cron"
import type {} from "koishi-plugin-monetary"*/

export const name = "-lnnbot-patron"

export const inject = ["cache", "cron", "database", "monetary"]

/*interface Patron {
  uid: number
  name: string
  ctime: Date
}
interface PatronBonus {
  uid: number
  bonus: number
  days: number
  total_days: number
}
declare module "koishi" {
  interface Tables {
    "lnnbot.patron": Patron
    "lnnbot.patron_bonus": PatronBonus
  }
}

declare module "@koishijs/cache" {
  interface Tables {
    "lnnbot/patron_bonus": any
  }
}*/

function getDate() {
  return Math.floor((Date.now() / 60000 - Time.getTimezoneOffset()) / 1440)
}

export function apply(ctx/*: Context*/) {
  ctx.model.extend(
    "lnnbot.patron",
    {
      uid: "unsigned",
      name: "string",
      ctime: "timestamp",
    },
    { primary: ["uid"] }
  )
  ctx.model.extend(
    "lnnbot.patron_bonus",
    {
      uid: "unsigned",
      bonus: "unsigned",
      days: "unsigned",
      total_days: "unsigned",
    },
    { primary: ["uid", "bonus"] }
  )

  async function award(data/*: Pick<PatronBonus, "uid" | "bonus">[] */, updateDays = true) {
    ctx.logger.info("award %o, updateDays=%o", data, updateDays)
    await ctx.database.upsert("monetary", r =>
      data.map(({ uid, bonus }) => ({
        uid,
        currency: "bonus",
        value: $.if($.gt(r.value, bonus), r.value, bonus),
      }))
    )
    if (updateDays)
      await ctx.database.set("lnnbot.patron_bonus", { $or: data }, r => ({
        days: $.subtract(r.days, 1),
      }))
  }

  let awardRunning = false
  async function awardAll(date/*: number*/) {
    if (awardRunning) return
    awardRunning = true
    try {
      let lastDate/*: number*/ = await ctx.cache.get("lnnbot/patron_bonus", "lastAwardedDate")
      if (lastDate >= date) {
        ctx.logger.info("skip award", lastDate, "-/>", date)
        return
      }
      ctx.logger.info("check award", lastDate, "->", date)
      await award(
        await ctx.database
          .select("lnnbot.patron_bonus")
          .where({ days: { $gt: 0 } })
          .groupBy(["uid"], { bonus: r => $.max(r.bonus) })
          .execute()
      )
      await ctx.cache.set("lnnbot/patron_bonus", "lastAwardedDate", date)
    } catch (exc) {
      ctx.logger.error(exc)
    } finally {
      awardRunning = false
    }
  }

  ctx.on("ready", () => awardAll(Math.floor(getDate())))
  ctx.cron("0 4 * * *", () => awardAll(Math.round(getDate())))

  ctx.command("patron", "")

  ctx
    .command("patron.add [user:user]", { checkArgCount: true, checkUnknown: true, authority: 4 })
    .option("id", "<natural:natural>")
    .option("name", "<string:string>")
    .option("days", "<posint:posint>")
    .option("bonus", "<posint:posint>")
    .before(({ options, session }, user) => {
      if (options.help) return
      const present = {
        user: options.id != null || !!user,
        days: options.days != null,
        bonus: options.bonus != null,
      }
      if (!Object.values(present).every(Boolean)) {
        const missing = Object.keys(present).filter(x => !present[x])
        return h.i18n(".missing-params", [
          [missing[0], ...missing.slice(1).flatMap(x => [...session.i18n("general.comma"), x])]
        ])
      }
    })
    .action(async ({ options, session }, user) => {
      let id = options.id
      let fallbackName = ""
      if (id == null) {
        const platform = user.split(":")[0]
        const pid = user.slice(platform.length + 1)
        const u = await ctx.database.getUser(platform, pid, ["id", "name"])
        if (!u) return session.i18n("commands.patron.messages.user-not-found")
        id = u.id
        fallbackName = u.name || `用户 ${id}`
      } else {
        const [u] = await ctx.database.get("user", id, ["name"])
        if (!u) return session.i18n("commands.patron.messages.user-not-found")
        fallbackName = u.name || `用户 ${id}`
      }

      ctx.database.upsert("lnnbot.patron", r => [
        {
          uid: id,
          name: options.name || $.if($.eq(r.name, ""), fallbackName, $.ifNull(r.name, fallbackName)),
          ctime: $.ifNull(r.ctime, new Date()),
        },
      ])
      ctx.database.upsert("lnnbot.patron_bonus", r => [
        {
          uid: id,
          bonus: options.bonus,
          days: $.add(r.days, options.days),
          total_days: $.add(r.total_days, options.days),
        },
      ])

      await award([{ uid: id, bonus: options.bonus }], false)

      if (session.onebot && !session.isDirect) {
        session.onebot._request("set_msg_emoji_like", {
          message_id: session.messageId,
          emoji_id: 124,
        })
        return
      }
      return "👌"
    })

  ctx
    .command("patron.reaward", { checkArgCount: true, checkUnknown: true, authority: 4 })
    //.option("id", "<natural:natural>")
    .action(async ({ options, session }, user) => {
      /*let id = options.id
      if (id == null) {
        const platform = user.split(":")[0]
        const pid = user.slice(platform.length + 1)
        const u = await ctx.database.getUser(platform, pid, ["id"])
        id = u.id
      }*/

      await award(
        await ctx.database
          .select("lnnbot.patron_bonus")
          .where({ days: { $gt: 0 } })
          .groupBy(["uid"], { bonus: r => $.max(r.bonus) })
          .execute(),
        false
      )

      if (session.onebot && !session.isDirect) {
        session.onebot._request("set_msg_emoji_like", {
          message_id: session.messageId,
          emoji_id: 124,
        })
        return
      }
      return "👌"
    })

  async function card(content/*: string*/) {
    const bg = Random.pick(await fsp.readdir("assets/cosmetic"))
    return /*html*/ `
      <html style="font-family: Noto Sans CJK SC, Noto Sans, Plangothic P1, Plangothic P2, sans-serif">
        <div style="background: url('file:///root/koishi-app/assets/cosmetic/@@_BG_@@')center/cover;width:375px;font-family:sans-serif">
          <div style="backdrop-filter:blur(1px)">
            <div style="padding:10px 10px 0">
              <div style="border-radius:5px;background:#ffffff99;padding:8px 16px;text-align:center">
                @@_CONTENT_@@
                <p style="font-size:9px;text-align:center">背景图：@@_BGSOURCE_@@</p>
              </div>
            </div>
            <img src="file:///root/koishi-app/assets/afdian.png" style="width:375px"/>
          </div>
        </div>
      </div>
    </html>`
      .replace("@@_CONTENT_@@", content)
      .replace(/\n\s*/g, "")
      .replace("@@_BG_@@", bg)
      .replace("@@_BGSOURCE_@@", bg.replace(/^.*?\)|\.[^.]*$|@.*/g, ""))
  }

  async function thanks(showEmptyMsg = true) {
    const patrons = await ctx.database.get(
      "lnnbot.patron",
      {},
      {
        sort: { ctime: "asc" },
        fields: ["name"],
      }
    )
    if (!patrons.length) {
      if (!showEmptyMsg) return ""
      return /*html*/ `
        <h3>目前还没有赞助者</h3>
        <p style="text-align:left">您的支持是我最大的动力。如果 LNNBot 对您有帮助，请考虑扫描下方二维码进行赞助，以支持我继续维护 LNNBot；赞助后，您还可以获得点数消耗减免。</p>
      `
    }
    return /*html*/ `
      <h3>感谢赞助者</h3>
      <p style="line-height:125%;display:flex;flex-wrap:wrap;gap:0 1em;justify-content:center">${patrons.map(p => `<span style="max-width:100%">${h.escape(p.name)}</span>`).join("")}</p>
    `
  }

  ctx.command("patron.list", { checkArgCount: true }).action(async () => {
    return card(await thanks())
  })

  async function getUserId(user/*: string*/) {
    const [, platform, pid] = user.match(/^(.*?):(.*)$/)
    const [data] = await ctx.database.get("binding", { platform, pid }, ["aid"])
    return data?.aid
  }

  ctx.command("patron.show [user:user]", { checkArgCount: true })
  .option("id", "<natural:natural>")
  .action(async ({ session, options }, user) => {
    const uid = options.id ?? (user ? await getUserId(user) : session.user.id)
    if (uid == null) return session.i18n("commands.patron.messages.user-not-found")
    const [p] = await ctx.database.get(
      "lnnbot.patron",
      { uid },
      { fields: ["name", "ctime"] }
    )
    if (!p) {
      if (uid !== session.user.id) return session.i18n(".user-is-not-patron")
      return card(
        /*html*/ `
          <h3>您还没有赞助 LNNBot</h3>
          <p style="text-align:left">您的支持是我最大的动力。如果 LNNBot 对您有帮助，请考虑扫描下方二维码进行赞助，以支持我继续维护 LNNBot；赞助后，您还可以获得点数消耗减免。</p>
        ` + (await thanks(false))
      )
    }
    const ents = await ctx.database.get(
      "lnnbot.patron_bonus",
      { uid },
      {
        fields: ["bonus", "days", "total_days"],
        sort: { bonus: "desc"},
      }
    )
    return card(/*html*/ `
      <h3>${uid === session.user.id ? "您的赞助信息" : "赞助者信息"}</h3>
      <p>用户序号：${uid}</p>
      <p>名称：${h.escape(p.name)}</p>
      <p>首次登记赞助时间：${h.escape(p.ctime.toLocaleString("zh-CN"))}</p>
      <h3>点数消耗减免额度</h3>
      ${ents
        .map(e => `<p>${e.bonus} 点 — 剩余 ${e.days} 天，共计 ${e.total_days} 天</p>`)
        .join("")}
      <h3>感谢您支持 LNNBot！</h3>
    `)
  })

  ctx.i18n.define("zh-CN", "commands.patron", {
    description: "赞助 LNNBot…",
    list: {
      description: "显示赞助者名单",
    },
    add: {
      description: "登记赞助者",
      options: {
        id: "用户序号",
        name: "赞助者昵称",
        days: "赞助期天数",
        bonus: "点数消耗减免额度",
      },
      messages: {
        "missing-params": "缺少参数：{0}",
      },
    },
    show: {
      description: "显示您当前的赞助情况",
      messages: {
        "user-is-not-patron": "该用户未赞助 LNNBot。",
      },
    },
    messages: {
      "user-not-found": "用户不存在…",
    },
  })
}
